import multiprocessing
import os.path
import warnings
from queue import Empty, Full, Queue
from typing import Optional

import cv2
import numpy as np

from cion.emccd.camera import EmccdCamera
from cion.emccd.capturing import capture_video
from cion.emccd.functions import find_ions


class PreviewGui:
    def __init__(self, dll_path: Optional[str] = None):
        self.control_queue = multiprocessing.Queue(16)
        self.event_queue = multiprocessing.Queue(16)

        self.process = multiprocessing.Process(
            target=_gui_process_func,
            args=(dll_path, self.control_queue, self.event_queue),
            daemon=True)
        self.process.start()

    def __del__(self):
        self.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        self.control_queue.put("exit")
        self.process.terminate()
        self.process.join()


def _gui_process_func(
        dll_path: Optional[str],
        control_queue: multiprocessing.Queue,
        event_queue: multiprocessing.Queue,
):
    win_name = "preview"
    cv2.imshow(win_name, np.zeros((512, 512)))

    cv2.createTrackbar("min_value", win_name, 0, 2000, lambda _: None)
    cv2.createTrackbar("max_value", win_name, 0, 2000, lambda _: None)
    cv2.setTrackbarPos("min_value", win_name, 500)
    cv2.setTrackbarPos("max_value", win_name, 700)

    img_queue = Queue(2)
    capturing = False

    def callback(img: np.ndarray):
        nonlocal capturing
        if capturing:
            i = 0
            while os.path.exists(f"captured{i}.npy"):
                i += 1
            np.save(f"captured{i}.npy", img)
            print(f"Saved captured{i}.npy")
            capturing = False

        # find ions
        max_point = np.max(img)
        min_point = np.min(img)
        img_find = np.asarray((img - min_point) * 255 / (max_point - min_point), dtype=np.uint8)
        circles = find_ions(img_find)
        if len(circles) > 5:
            circles = None

        # img preview
        min_value = cv2.getTrackbarPos("min_value", win_name)
        max_value = cv2.getTrackbarPos("max_value", win_name)
        img_preview = np.asarray((img - min_value) / (max_value - min_value) * 255, dtype=np.uint8)
        img_preview = cv2.cvtColor(img_preview, cv2.COLOR_GRAY2BGR, dst=img_preview)
        for x, y, r in (circles or ()):
            x, y, r = int(x), int(y), int(r)
            cv2.circle(img_preview, (x, y), r, (0, 255, 255), 1)

        try:
            img_queue.put(img_preview, timeout=1)
        except Full:
            warnings.warn("skipping one frame (because of the lag of gui)")

    with (
        EmccdCamera.from_path(dll_path) as camera,
        capture_video(camera, callback)
    ):
        while True:
            try:
                img = img_queue.get(timeout=1)
            except Empty:
                warnings.warn("skipping one frame (because of the lag of source)")
                continue

            cv2.imshow(win_name, img)

            kn = cv2.waitKey(1)
            if kn == ord('p'):
                capturing = True

            try:
                cmd = control_queue.get_nowait()
            except Empty:
                cmd = None
            except BrokenPipeError:
                break

            if cmd == "exit":
                break

            visible = cv2.getWindowProperty(win_name, cv2.WND_PROP_VISIBLE)
            if not visible:
                break

    cv2.destroyAllWindows()

    try:
        event_queue.put(("exit", None))
    except BrokenPipeError:
        pass
